/***************************************
 Felipe L Amado       6797205
 Renato A V Nishimori 5121731
 *****************************************/

#include "monitor.hpp"
#include <cstdio>
#include <time.h>
#include <assert.h>
#include <iostream>
#include <sstream>

Monitor::Monitor(int nc, int cap) {

	numeroCarros = nc;
	capacidade = cap;
	passageirosNoCarro = (int*) malloc(numeroCarros * sizeof(int));
	carroAndando = (int*) malloc(numeroCarros * sizeof(int));
	passDentroDoCarro = (stringstream**) malloc(numeroCarros * sizeof(stringstream*));

	for (int i = 0; i < numeroCarros; i++) {
		passageirosNoCarro[i] = 0;
		passDentroDoCarro[i] = new stringstream;
	}

	sem_unlink("mutex");
    mutex = sem_open("mutex", O_CREAT, 0, 1);
    sem_unlink("carroCheio");
    carroCheio = sem_open("carroCheio", O_CREAT, 0, 0);
   	sem_unlink("prontoParaSair");
    prontoParaSair = sem_open("prontoParaSair", O_CREAT, 0, 0);
    sem_unlink("podeEmbarcar");
    podeEmbarcar = sem_open("podeEmbarcar", O_CREAT, 0, 0);
    sem_unlink("podeDesembarcar");
    podeDesembarcar = sem_open("podeDesembarcar", O_CREAT, 0, 0);
    sem_unlink("proximoCarroPodeCarregar");
    proximoCarroPodeCarregar = sem_open("proximoCarroPodeCarregar", O_CREAT, 0, 0);
    sem_unlink("temQuemEmbarcar");
    temQuemEmbarcar = sem_open("temQuemEmbarcar", O_CREAT, 0, 0);

	esperandoEmbarcar = 0;
	carroAtualACarregar = 0;
	carroAtualADescarregar = 0;
	proximoCarroACarregar = 0;
	passCont = 0;
	tempo = 0;
}

Monitor::~Monitor() {
	sem_destroy(mutex);
	sem_destroy(carroCheio);
	sem_destroy(prontoParaSair);
	sem_destroy(podeEmbarcar);
	sem_destroy(podeDesembarcar);
	sem_destroy(proximoCarroPodeCarregar);
	sem_destroy(temQuemEmbarcar);

	for(int i = 0; i < (int)semDoPassageiro.size(); ++i) {
		if (semDoPassageiro[i]) {
			sem_destroy(semDoPassageiro[i]);
		}
	}

	free(passageirosNoCarro);
	free(carroAndando);
	free(passDentroDoCarro);
}

void Monitor::carrega() {
	sem_wait(mutex);

	imprimeEstado();

	/* Acho que tah meio redundante aqui, mas tava dando pau */
	int idCarro = proximoCarroACarregar;
	proximoCarroACarregar = (proximoCarroACarregar + 1) % numeroCarros;

	while (idCarro != carroAtualACarregar) {
		wait(PROXIMO_PODE_CARREGAR);
	}

	/* Carro disponivel para embarcarem */
	esperandoPassageiros = true;

	for (int i = 0; i < capacidade; ++i) {
		wait(TEM_QUEM_EMBARCAR);
		signal(PODE_EMBARCAR);
	}
	
	//printf("Carro %d esperando passageiros.\n", idCarro);

	/* Espera o carro ficar cheio (sinalizado pelo ultimo passageiro) */
	wait(CARRO_CHEIO);

	/* Carro cheio, avisa os passageiros que estavam esperando */
	esperandoPassageiros = false;
	signal_all(PRONTO_PARA_SAIR);

	carroAndando[carroAtualACarregar] = 1;
	carroAtualACarregar = (carroAtualACarregar + 1) % numeroCarros;

	signal(PROXIMO_PODE_CARREGAR);

	sem_post(mutex);
}

void Monitor::descarrega() {
	sem_wait(mutex);

	imprimeEstado();

	carroAndando[carroAtualACarregar] = 0;
	/* Sinaliza pros passageiros sairem do carro */
	signal_all(PODE_DESEMBARCAR);
	passageirosNoCarro[carroAtualADescarregar] = 0;
	delete passDentroDoCarro[carroAtualACarregar];
	passDentroDoCarro[carroAtualACarregar] = new stringstream;
	//printf("Carro %d descarregou.\n", carroAtualADescarregar);
	carroAtualADescarregar = (carroAtualADescarregar + 1) % numeroCarros;

	sem_post(mutex);
}

void Monitor::pegaCarona(Passageiro pass) {
	sem_wait(mutex);

	pass.tempoDeChegada = tempo++;
	
	/* Cuidando do mutex do passageiro */
	while (pass.id >= (int)semDoPassageiro.size()) {
		semDoPassageiro.resize(semDoPassageiro.size()*2 + 1, NULL);
	}
	
	stringstream ss;
	ss << "semaphore" << pass.id;
	sem_unlink(ss.str().c_str());
	semDoPassageiro[pass.id] = sem_open(ss.str().c_str(), O_CREAT, 0, 0);

	imprimeEstado();

	/* Enquanto nao tem carro disponivel, esperem */
	do {
	//	printf("Passageiro %d esperando o carro.\n", pass.id);
		esperandoEmbarcar++;
		signal(TEM_QUEM_EMBARCAR);
		wait(PODE_EMBARCAR, pass);
		esperandoEmbarcar--;
	} while (!esperandoPassageiros || passageirosNoCarro[carroAtualACarregar] == capacidade);

	passageirosNoCarro[carroAtualACarregar]++;
	*(passDentroDoCarro[carroAtualACarregar]) << (pass.bilheteDourado ? " D" : " ");
	*(passDentroDoCarro[carroAtualACarregar]) << pass.id;

	//printf("Passageiro %d vai entrar no carro %d.\n", pass.id, carroAtualACarregar);

	/* Ultimo passageiro */
	if (passageirosNoCarro[carroAtualACarregar] == capacidade) {
		signal(CARRO_CHEIO);
	}

	wait(PRONTO_PARA_SAIR);

	/* Espera o carro em que esta comecar a descarregar */
	wait(PODE_DESEMBARCAR);
	//printf("Passageiro %d descendo do carro %d.\n", idPassageiro,
	//		(carroAtualADescarregar + numeroCarros - 1) % numeroCarros);

	sem_post(mutex);
}

void Monitor::wait(int cv) {
	sem_post(mutex);
	switch (cv) {
	case CARRO_CHEIO:
		sem_wait(carroCheio);
		break;
	case PRONTO_PARA_SAIR:
		sem_wait(prontoParaSair);
		break;
	case PODE_EMBARCAR:
		sem_wait(podeEmbarcar);
		break;
	case PODE_DESEMBARCAR:
		sem_wait(podeDesembarcar);
		break;
	case PROXIMO_PODE_CARREGAR:
		sem_wait(proximoCarroPodeCarregar);
		break;
	case TEM_QUEM_EMBARCAR:
		sem_wait(temQuemEmbarcar);
		break;
	}
	sem_wait(mutex);
}

void Monitor::wait(int cv, Passageiro rank) {
	
	switch (cv) {
	case CARRO_CHEIO:
		sem_post(mutex);
		sem_wait(carroCheio);
		sem_wait(mutex);
		break;
	case PRONTO_PARA_SAIR:
		sem_post(mutex);
		sem_wait(prontoParaSair);
		sem_wait(mutex);
		break;
	case PODE_EMBARCAR:

		do {
			filaDeP.push(rank);
			sem_post(mutex);
			sem_wait(semDoPassageiro[rank.id]);
			sem_wait(mutex);
		} while (passCont == 0);
		passCont--;

		break;
	case PODE_DESEMBARCAR:
		sem_post(mutex);
		sem_wait(podeDesembarcar);
		sem_wait(mutex);
		break;
	case PROXIMO_PODE_CARREGAR:
		sem_post(mutex);
		sem_wait(proximoCarroPodeCarregar);
		sem_wait(mutex);
		break;
	}
}

void Monitor::signal(int cv) {
	Passageiro pass;
	switch (cv) {
	case CARRO_CHEIO:
		sem_post(carroCheio);
		break;
	case PRONTO_PARA_SAIR:
		sem_post(prontoParaSair);
		break;
	case PODE_EMBARCAR:
		passCont++;
		if (filaDeP.size() != 0) {
			pass = filaDeP.top();
			filaDeP.pop();
			sem_post(semDoPassageiro[pass.id]);
		}
		break;
	case PODE_DESEMBARCAR:
		sem_post(podeDesembarcar);
		break;
	case PROXIMO_PODE_CARREGAR:
		sem_post(proximoCarroPodeCarregar);
		break;
	case TEM_QUEM_EMBARCAR:
		sem_post(temQuemEmbarcar);
		break;
	}
}

void Monitor::signal_all(int cv) {
	if (cv == PRONTO_PARA_SAIR) {
		for (int i = 0; i < capacidade; i++) {
			sem_post(prontoParaSair);
		}
	} 
	else if (cv == PODE_EMBARCAR) {
		for (int i = 0; i < capacidade; i++) {
			sem_post(podeEmbarcar);
		}
	} 
	else if (cv == PODE_DESEMBARCAR) {
		for (int i = 0; i < capacidade; i++) {
			sem_post(podeDesembarcar);
		}
	}
}

void Monitor::imprimeEstado() {

	vector < Passageiro > vec;
	int tamanho = filaDeP.size();

	for (int i = 0; i < numeroCarros; ++i) {
		cout << "Carro " << i << (carroAndando[i] ? ", em movimento," : ", parado,");
		cout << " tem " << passageirosNoCarro[i] << " passageiros:";
		if (passDentroDoCarro[i]) {
			cout << passDentroDoCarro[i]->str();
		} 
		cout << endl;
	}

	for (int i = 0; i < tamanho; ++i) {
		Passageiro pass = filaDeP.top();
		filaDeP.pop();
		vec.push_back(pass);
	}

	cout << "Fila tem " << esperandoEmbarcar << " pessoas:";
	for (int i = 0; i < tamanho; ++i) {
		cout << (vec[i].bilheteDourado ? " D" : " ");
		cout << vec[i].id << ":" << vec[i].vez;
	}
	cout << endl << endl;

	for (int i = 0; i < tamanho; ++i) {
		filaDeP.push(vec[i]);
	}

}

int Monitor::empty(int cv) {
	int value;
	switch (cv) {
	case CARRO_CHEIO:
		sem_getvalue (carroCheio, &value);
		return value != 0;
	case PRONTO_PARA_SAIR:
		sem_getvalue (prontoParaSair, &value);
		return value != 0;
	case PODE_EMBARCAR:
		return filaDeP.empty();
	case PODE_DESEMBARCAR:
		sem_getvalue (podeDesembarcar, &value);
		return value != 0;
	case PROXIMO_PODE_CARREGAR:
		sem_getvalue (proximoCarroPodeCarregar, &value);
		return value != 0;
	case TEM_QUEM_EMBARCAR:
		sem_getvalue (temQuemEmbarcar, &value);
		return value != 0;
	}
	return 1;
}

Passageiro Monitor::minrank(int cv) {
	Passageiro res;
	if (cv == PODE_EMBARCAR) {
		res = filaDeP.top();
	}
	return res;
}